package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
)

type TypeInfo struct {
	Name  string
	Kind  reflect.Kind
	Child []*TypeInfo
}

func NewTypeInfo() *TypeInfo {
	return &TypeInfo{
		Child: make([]*TypeInfo, 0),
	}
}

func (this *TypeInfo) SetType(v interface{}) *TypeInfo {
	switch vv := v.(type) {
	default:
		this.SetType(reflect.TypeOf(v))
	case reflect.Value:
		this.SetType(vv.Type())
	case reflect.Type:
		this.Kind = vv.Kind()
		switch this.Kind {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		case reflect.Float32, reflect.Float64:
		case reflect.String:
		case reflect.Slice:
			info := NewTypeInfo()
			info.SetType(vv.Elem()) // parse slice elem type
			this.Append(info)
		case reflect.Struct:
			for i := 0; i < vv.NumField(); i++ {
				info := NewTypeInfo()
				info.Name = vv.Field(i).Name
				info.SetType(vv.Field(i).Type)
				this.Append(info)
			}
		default:
			panic(fmt.Sprintf("unexpected kind: %s", vv.Kind()))
		}
	}
	return this
}

func (this *TypeInfo) SetInt32(name string) *TypeInfo {
	this.Name = name
	this.Kind = reflect.Int32
	return this
}

func (this *TypeInfo) SetString(name string) *TypeInfo {
	this.Name = name
	this.Kind = reflect.String
	return this
}

func (this *TypeInfo) SetFloat32(name string) *TypeInfo {
	this.Name = name
	this.Kind = reflect.Float32
	return this
}

func (this *TypeInfo) MakeType() reflect.Type {
	switch this.Kind {
	case reflect.Int, reflect.Int32, reflect.Int64:
		return reflect.TypeOf(int32(0))
	case reflect.String:
		return reflect.TypeOf(string(""))
	case reflect.Float32, reflect.Float64:
		return reflect.TypeOf(float32(0))
	case reflect.Slice:
		return reflect.SliceOf(
			this.Child[0].MakeType(),
		)
	case reflect.Struct:
		fields := []reflect.StructField{}
		for i := 0; i < len(this.Child); i++ {
			fields = append(fields,
				reflect.StructField{
					Name: this.Child[i].Name,
					Type: this.Child[i].MakeType(),
				},
			)
		}
		return reflect.StructOf(fields)
	}
	return nil
}

func (this *TypeInfo) MakeStruct(name string) string {
	if this.Kind != reflect.Struct {
		panic("Not Struct Type")
	}

	b := &bytes.Buffer{}
	b.WriteString(fmt.Sprintf("type %s struct {\n", name))
	this.makeStruct(b)
	b.WriteString("}\n")

	return b.String()
}

func (this *TypeInfo) makeStruct(b *bytes.Buffer) {
	switch this.Kind {
	case reflect.Slice:
		kind := this.Child[0].Kind
		switch kind {
		case reflect.Int, reflect.Int32, reflect.Int64:
			b.WriteString(fmt.Sprintf("%s []int32\n", this.Name))
		case reflect.String:
			b.WriteString(fmt.Sprintf("%s []string\n", this.Name))
		case reflect.Float32, reflect.Float64:
			b.WriteString(fmt.Sprintf("%s []float32\n", this.Name))
		case reflect.Struct:
			b.WriteString(fmt.Sprintf("%s [] struct {\n", this.Name))
			this.Child[0].makeStruct(b)
			b.WriteString(fmt.Sprintf("}\n"))
		}
	case reflect.Struct:
		for i := 0; i < len(this.Child); i++ {
			switch this.Child[i].Kind {
			case reflect.Int, reflect.Int32, reflect.Int64:
				b.WriteString(fmt.Sprintf("%s int32\n", this.Child[i].Name))
			case reflect.String:
				b.WriteString(fmt.Sprintf("%s string\n", this.Child[i].Name))
			case reflect.Float32, reflect.Float64:
				b.WriteString(fmt.Sprintf("%s float32\n", this.Child[i].Name))
			case reflect.Slice:
				this.Child[i].makeStruct(b)
			case reflect.Struct:
				b.WriteString(fmt.Sprintf("%s struct {\n", this.Child[i].Name))
				this.Child[i].makeStruct(b)
				b.WriteString(fmt.Sprintf("}\n"))
			}
		}
	}
}

func (this *TypeInfo) MakeValue() reflect.Value {
	return reflect.New(this.MakeType()).Elem()
}

func (this *TypeInfo) Append(v *TypeInfo) {
	this.Child = append(this.Child, v)
}

func ExampleMakeType() {
	fields := []reflect.StructField{
		{
			Name: "Height",
			Type: reflect.TypeOf(float64(0)),
			Tag:  `json:"height"`,
		},
		{
			Name: "Age",
			Type: reflect.TypeOf(int(0)),
			Tag:  `json:"age"`,
		},
	}

	typ := reflect.StructOf(fields)
	e := reflect.New(typ).Elem()
	e.Field(0).SetFloat(0.4)
	e.Field(1).SetInt(2)
	v := e.Addr().Interface()

	b, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}

	fmt.Println(typ)
	fmt.Println(v)
	fmt.Println(string(b))
	fmt.Println(e.Kind())

	info := NewTypeInfo()
	info.SetType(e)
	fmt.Println(info.MakeType())
	e2 := info.MakeValue()
	e2.Field(0).SetFloat(0.1)
	e2.Field(1).SetInt(2)
	fmt.Println(e2.Addr().Interface())

	type Student struct {
		Name string
		Val  []int32
	}

	{
		info := NewTypeInfo()
		info.SetType(reflect.SliceOf(reflect.TypeOf(Student{})))
		fmt.Println(info.MakeType())
		stus := info.MakeValue()
		for i := 0; i < 2; i++ {
			stu := info.Child[0].MakeValue()
			stu.Field(0).SetString(fmt.Sprintf("Astone-%d", i))
			for j := 0; j < 2; j++ {
				val := info.Child[0].Child[1].Child[0].MakeValue()
				val.SetInt(int64(i + j))
				stu.Field(1).Set(reflect.Append(stu.Field(1), val))
			}
			stus.Set(reflect.Append(stus, stu))
		}
		fmt.Println(stus.Interface())
	}
}
